package server.commands;

import client.MapleCharacter;
import client.MapleClient;
import database.DatabaseConnection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import tools.MaplePacketCreator;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;

public class CommandProcessor {

    private static final Logger log = LogManager.getLogger(CommandProcessor.class.getName());
    private static final Map<String, CommandObject> commands = new LinkedHashMap<>();
    private static final Map<PlayerGMRank, Map<String, Set<String>>> overrideCommands = new LinkedHashMap<>();


    private static int getReqGMLevel(String simpleName, PlayerGMRank defRank) {
        PlayerGMRank ret = defRank;
        for (Map.Entry<PlayerGMRank, Map<String, Set<String>>> entry : overrideCommands.entrySet()) {
            if (entry.getValue().containsKey(simpleName)) {
                ret = ret.ordinal() > entry.getKey().ordinal() ? ret : entry.getKey();
            }

        }
        return ret.getLevel();
    }

    public static void init() {
        commands.clear();
        overrideCommands.clear();
        try {
            Properties properties = new Properties();
            try (FileInputStream fileInputStream = new FileInputStream("config/command.properties")) {
                try (InputStreamReader streamReader = new InputStreamReader(fileInputStream, "UTF-8")) {
                    try (BufferedReader bufferedReader = new BufferedReader(streamReader)) {
                        properties.load(bufferedReader);
                    }
                }
            }
            properties.forEach((o1, o2) -> {
                String key = (String) o1;
                String value = (String) o2;
                String[] split = key.split("\\.");
                if (split.length != 2) {
                    return;
                }
                PlayerGMRank rank = PlayerGMRank.valueOf(split[0].toUpperCase());
                String defcommand = split[1].toLowerCase();
                if (defcommand.equals("op")) {
                    return;
                }
                value = value.replace("，", ",");
                String[] arrayCommands = value.split(",");
                for (String command : arrayCommands) {
                    overrideCommands.computeIfAbsent(rank, k -> new HashMap<>())
                            .computeIfAbsent(defcommand, k -> new HashSet<>())
                            .add(command.toLowerCase());
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("导入GM命令出现错误");
        }

        Class<?>[] CommandFiles = {
                PlayerCommand.class,
                InternCommand.class,
                GMCommand.class,
                AdminCommand.class,
                DonatorCommand.class,
                SuperDonatorCommand.class,
                SuperGMCommand.class
        };

        for (Class<?> clasz : CommandFiles) {
            try {
                PlayerGMRank rankNeeded = (PlayerGMRank) clasz.getMethod("getPlayerLevelRequired", new Class<?>[]{}).invoke(null, (Object[]) null);
                Class<?>[] a = clasz.getDeclaredClasses();
                for (Class<?> c : a) {
                    try {
                        if (!Modifier.isAbstract(c.getModifiers()) && !c.isSynthetic()) {
                            Object o = c.newInstance();
                            boolean enabled;
                            try {
                                enabled = c.getDeclaredField("enabled").getBoolean(c.getDeclaredField("enabled"));
                            } catch (NoSuchFieldException ex) {
                                enabled = true; //Enable all coded commands by default.
                            }
                            if (o instanceof CommandExecute && enabled) {
                                String command = c.getSimpleName().toLowerCase();
                                commands.put(c.getSimpleName().toLowerCase(), new CommandObject((CommandExecute) o, getReqGMLevel(c.getSimpleName().toLowerCase(), rankNeeded)));
                                overrideCommands.computeIfAbsent(rankNeeded, k -> new HashMap<>())
                                        .computeIfAbsent(command, k -> new HashSet<>())
                                        .add(command);
                            }
                        }
                    } catch (Exception ex) {
                        log.error(ex);
                    }
                }
            } catch (Exception ex) {
                log.error(ex);
            }
        }
    }

    private static void sendDisplayMessage(MapleClient c, String msg, CommandType type) {
        if (c.getPlayer() == null) {
            return;
        }
        switch (type) {
            case NORMAL:
                c.getPlayer().dropMessage(6, msg);
                break;
            case TRADE:
                c.getPlayer().dropMessage(-2, "错误 : " + msg);
                break;
            case POKEMON:
                c.getPlayer().dropMessage(-3, "(..." + msg + "..)");
                break;
        }

    }

    public static void dropHelp(MapleClient c) {
        StringBuilder sb = new StringBuilder();
        int gmLevel = c.getPlayer().getGMLevel();
        for (int i = 0; i < gmLevel; i++) {
            PlayerGMRank rankByLevel = PlayerGMRank.getPlayerGMRankByLevel(i);
            sb.append("#fs18#").append(rankByLevel.getName()).append("#fs#\r\n");
            Optional.ofNullable(overrideCommands.get(rankByLevel)).ifPresent(stringSetMap ->
                    stringSetMap.forEach((key, value) -> {
                        CommandObject co = commands.get(key);
                        if (co == null) {
                            System.out.println(key);
                            return;
                        }
                        char commandPrefix = rankByLevel.getCommandPrefix();
                        sb.append("#r#e");
                        sb.append(commandPrefix).append(key).append("\t别名: ");
                        value.stream().filter(s -> !s.isEmpty() && !s.equals(key)).forEach(s -> sb.append(s).append(", "));
                        sb.append("#k#n\r\n");
                        sb.append("用法: ").append(commandPrefix).append(key).append(" ").append(co.getExe().getParameter());
                        sb.append("\r\n");
                    }));
            sb.append("\r\n\r\n");
        }
//        c.getPlayer().dropMessage(6, sb.toString());
        c.announce(MaplePacketCreator.sendPolice(sb.toString()));
    }

    public static boolean processCommand(MapleClient c, String line, CommandType type) {
        int gmLevel = c.getPlayer().getGMLevel();
        line = line.replace('！', '!');
        String[] splitted = line.split(" ");
        splitted[0] = splitted[0].toLowerCase();
        String command = splitted[0].substring(1, splitted[0].length());

        if (command.isEmpty()) {
            return false;
        }

        for (Map.Entry<PlayerGMRank, Map<String, Set<String>>> entry : overrideCommands.entrySet()) {
            if (gmLevel >= entry.getKey().getLevel()) {
                for (Map.Entry<String, Set<String>> commands : entry.getValue().entrySet()) {
                    if (commands.getValue().contains(command)) {
                        command = commands.getKey();
                        break;
                    }
                }
            }
        }

        CommandObject co = commands.get(command);

        PlayerGMRank commandLevel = PlayerGMRank.getPlayerGMRankByCommandPrefix(line.charAt(0));

        if (commandLevel != null && commandLevel.getLevel() <= gmLevel) {
            if (gmLevel <= PlayerGMRank.SUPERDONATOR.getLevel()) {
                if (PlayerGMRank.isPlayerCommandPrefix(line.charAt(0))) {
                    if (co == null || co.getReqGMLevel() > gmLevel || co.getType() != type) {
                        sendDisplayMessage(c, "输入的玩家命令不存在.", type);
                        return true;
                    }
                    try {
                        co.execute(c, splitted); //Don't really care about the return value. ;D
                    } catch (Exception e) {
                        sendDisplayMessage(c, "使用命令出现错误.", type);
                        if (c.getPlayer().isGM()) {
                            sendDisplayMessage(c, "错误: " + e, type);
                            log.error(e);
                        }
                    }
                    return true;
                }
            } else if (PlayerGMRank.isGMCommandPrefix(line.charAt(0)) || gmLevel >= commandLevel.getLevel()) { //Redundant for now, but in case we change symbols later. This will become extensible.
                if (command.equals("help")) {
                    dropHelp(c);
                    return true;
                }

                if (co == null || co.getReqGMLevel() > gmLevel || co.getType() != type) {
                    sendDisplayMessage(c, "输入的命令不存在.", type);
                    return true;
                }

                if (c.getPlayer().getGMLevel() >= co.getReqGMLevel()) {
                    int ret = 0;
                    try {
                        ret = co.execute(c, splitted);
                    } catch (ArrayIndexOutOfBoundsException x) {
                        sendDisplayMessage(c, "使用命令出错，该命令必须带参数才能使用: " + x, type);
                    } catch (Exception e) {
                        log.error(e);
                    }
                    if (ret > 0 && c.getPlayer() != null) { //incase d/c after command or something
                        if (c.getPlayer().isGM()) {
                            logCommandToDB(c.getPlayer(), line, "gmlog");
                        } else {
                            logCommandToDB(c.getPlayer(), line, "internlog");
                        }
                    }
                } else {
                    sendDisplayMessage(c, "您的权限等级不足以使用次命令.", type);
                }
                return true;
            }
        }
        return false;
    }

    private static void logCommandToDB(MapleCharacter player, String command, String table) {
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement("INSERT INTO " + table + " (cid, name, command, mapid) VALUES (?, ?, ?, ?)")) {
                ps.setInt(1, player.getId());
                ps.setString(2, player.getName());
                ps.setString(3, command);
                ps.setInt(4, player.getMap().getId());
                ps.executeUpdate();
            }
        } catch (SQLException ex) {
            log.error(ex);
        }
    }
}
